# -*- coding: UTF-8 -*-
"""
Created on 02.09.2014

@author: MathiasAr
"""

import threading, exceptions, thread, time, sys
import wx

from MediaHelper import IsSupportedFile
from ImageHelper import GetImageOrientation

class WxAsyncImageLoader(threading.Thread):

    def __init__(self, image_size=(50, 50), crop_image=True, quality=wx.IMAGE_QUALITY_NORMAL):
        """
        :param image_size: None - no scaling, list or tuple with [width, height] - scale [and crop] image to given size
        :param crop_image: cut image to requested size
        :param quality: defines the quality of scaling the image (if required)
        """
        threading.Thread.__init__(self)
        if isinstance(image_size, (tuple, list)):
            if len(image_size) != 2:
                raise exceptions.TypeError('Argument image_size must be tuple with 2 values for width and height (X, Y)')
            self._im_size = image_size
        elif isinstance(image_size, wx.Size):
            self._im_size = (image_size.GetWidth(), image_size.GetHeight())
        else:
            self._im_size = None
        self._crop = crop_image
        self._quality = quality
        self._images = []
        self._lock = thread.allocate_lock()
        self._quit = False
        self._has_stopped = False
        self._result = {}
        self._events = []

    def connect(self, win, func, data=None):
        """
        :param win:
        :param func:
        :param data:
        :return:
        """
        event_id = wx.NewId()
        win.Connect(-1, -1, event_id, func)
        event = wx.PyEvent()
        event.SetEventType(event_id)
        self._events.append((event, win, func, data))
        pass

    def stopping(self):
        self._lock.acquire()
        stopping = self._quit
        self._lock.release()
        return stopping
        
    def quit(self):
        self._quit = True
    
    def start(self, file_names=[]):
        self._images.extend(file_names)
        return threading.Thread.start(self)

    def run(self):
        while not self._quit:
            self._lock.acquire()
            next_item = len(self._images) > 0
            if next_item:
                f = self._images.pop(0)
            self._lock.release()
            # leave the loop, when all items processed
            if not next_item:
                break
            # check item
            if not IsSupportedFile(f):
                continue
            # load the image
            try:
                # load info from file
                im = wx.Image(name=f)
                if not im.LoadFile(f):
                    sys.stderr.write("WxAsyncImageLoader: %s --> Couldn't load image\n" % f)
                    continue
                # check the EXIF orientation flags
                exif_orient = GetImageOrientation(f)
                im_size = get_oriented_size((im.GetWidth(), im.GetHeight()), exif_orient)

                # resize required?
                if self._im_size is not None:
                    width = self._im_size[0]
                    height = self._im_size[1]
                    if self._crop:
                        scale, offs_x, offs_y = crop_scale(im_size[0], im_size[1], self._im_size[0], self._im_size[1])
                    else:
                        new_x, new_y = fit_scale(im_size[0], im_size[1], self._im_size[0], self._im_size[1])
                        width = new_x
                        height = new_y
                    # check orientation
                    if im_size[0] != im.GetWidth():
                        # image is rotated
                        new_x = height
                        height = width
                        width = new_x
                    # scale
                    im = im.Rescale(width=width, height=height, quality=wx.IMAGE_QUALITY_NORMAL)

                # orient the image according to EXIF flags
                im = auto_orient(im, exif_orient)

                # add image instance to result dictionary
                self._result.setdefault(f, im)

                # check to exit
                if self._quit:
                    break

                # fire events
                for event, win, func, data in self._events:
                    event.data = (im, data)
                    wx.PostEvent(win, event)

                # give other threads time
                time.sleep(0.1)
            except:
                sys.stderr.write('WxAsyncImageLoader: %s --> %s\n ' % (f, str(sys.exc_value)))
                pass
            pass
        
        # finish this thread
        self._quit = True
        self._has_stopped = True
        pass

    @property
    def get_result(self):
        """Call after thread has stopped.
        :rtype : dict
        :return: Dictionary with loaded file names as key and value with loaded instances of type wx.Image
        """
        if not self._has_stopped:
            raise exceptions.AssertionError("Thread is already active. \
            Stop thread first and/or wait till finished working.")
        return self._result


def get_oriented_size(raw_size=None, exif_orientation=0):
    if not isinstance(raw_size, (tuple, list)) and len(raw_size) != 2:
        return 0, 0
    if exif_orientation == 5 or exif_orientation == 8 or exif_orientation == 6 or exif_orientation == 7:
        return raw_size[1], raw_size[0]
    return raw_size[0], raw_size[1]


def fit_scale(width, height, max_width, max_height):
    """fitScale() --> return: (width, height)
    :rtype : tuple
    """

    if max_width == 0 or width == 0 or max_height == 0 or height == 0:
        return (0, 0)

    # calculate ratio
    sourceRatio = float(width) / float(height)
    targetRatio = float(max_width) / float(max_height)

    # Calculate X and Y size
    newWidth = max_width
    newHeight = max_height
    if sourceRatio > targetRatio:
        newHeight = float(newWidth) / sourceRatio
    elif sourceRatio < targetRatio:
        newWidth = float(newHeight) * sourceRatio
        pass

    # return new, scaled dimensions
    return (int(newWidth), int(newHeight))


def crop_scale(width, height, new_width, new_height):
    """cropScale() --> return: (scale, offsetX, offsetY)
    :rtype : tuple
    """
    if new_height == 0 or new_width == 0:
        return 0, 0, 0

    targetRatio = float(new_width) / float(new_height)

    # crop image to the target ratio
    srcX = float(width)
    srcY = float(height)
    sourceRatio = srcX / srcY
    scale = 1.0

    if sourceRatio >= targetRatio:
        scale = float(new_height) / srcY
        xSrcOffs = int((((srcY * targetRatio) - srcX) * scale) / 2.0)
        ySrcOffs = 0
    else:
        scale = float(new_width) / srcX
        xSrcOffs = 0
        ySrcOffs = int((((srcX / targetRatio) - srcY) * scale) / 2.0)
        pass

    return scale, xSrcOffs, ySrcOffs


def auto_orient(im=wx.Image, exif_orientation=0):
    """

    :rtype : wx.Image
    """
    if exif_orientation == 2:
        im = im.Mirror(horizontally=True)
    if exif_orientation == 4 or exif_orientation == 5 or exif_orientation == 7:
        im = im.Mirror(horizontally=False)
    if exif_orientation == 3:
        im = im.Rotate180()
    if exif_orientation == 5 or exif_orientation == 8:
        im = im.Rotate90()
    if exif_orientation == 6 or exif_orientation == 7:
        im = im.Rotate180()
        im = im.Rotate90()
    return im